﻿/**
 * Copyright (C) 2011 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Internal.Util;
using NGuice.Inject.Spi;

namespace NGuice.Inject.Internal
{
    //complete!
    /// <summary>
    /// 构建注入器树。注入器树由主注入器、附加的子注入器（对每一个私有环境的 Binder.NewPrivateBinder()需要）组成。
    /// 主注入器不必是顶层级别的注入器。注入器构造期间由两个阶段组成：
    /// <list type="number">
    /// <item>
    /// 静态构建阶段：在这个阶段中，我们解释命令、创建绑定、内省依赖。在这个阶段期间，我们持有一个锁以便确保与父
    /// 注入器一致。在这个阶段将不运行任何用户代码
    /// </item>
    /// <item>
    /// 动态注入：在这个阶段，我们调用用户代码。我们注入请求注入的成员。这可能会请求用户的对象被创建以及他们的提供者
    /// 被调用。我们将提前创建单例。在这个阶段，用户代码可能会启动其他线程。这个阶段不为使用Stage.TOOL工具阶段创建
    /// 的注入器运行
    /// </item>
    /// </list>
    /// </summary>
    public sealed class InternalInjectorCreator
    {
        private readonly Stopwatch stopwatch = new Stopwatch();
        private readonly Errors errors = new Errors();

        private readonly Initializer initializer = new Initializer();
        private readonly BindingProcessor bindingProcesor;
        private readonly InjectionRequestProcessor injectionRequestProcessor;

        private readonly InjectorShell.Builder shellBuilder = new InjectorShell.Builder();
        private IList<InjectorShell> shells;

        public InternalInjectorCreator()
        {
            injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer);
            bindingProcesor = new BindingProcessor(errors, initializer);
        }

        public InternalInjectorCreator Stage(Stage stage)
        {
            shellBuilder.Stage(stage);
            return this;
        }

        /// <summary>
        /// 设置注入器的parent能够被构造
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        public InternalInjectorCreator ParentInjector(InjectorImpl parent)
        {
            shellBuilder.Parent(parent);
            return this;
        }

        public InternalInjectorCreator AddModules(IEnumerable<IModule> modules)
        {
            shellBuilder.AddModules(modules);
            return this;
        }

        public Injector Build()
        {
            if (shellBuilder == null)
            {
                throw new AssertionError("Already built, builders are not reusable.");
            }

            // Synchronize while we're building up the bindings and other injector state. This ensures that
            // the JIT bindings in the parent injector don't change while we're being built
            lock (shellBuilder.Lock())
            {
                shells = shellBuilder.Build(bindingProcesor, stopwatch, errors);
                stopwatch.ResetAndLog("Injector construction");

                InitializeStatically();
            }

            InjectDynamically();

            if (shellBuilder.GetStage() == Inject.Stage.TOOL)
            {
                // wrap the primaryInjector in a ToolStageInjector
                // to prevent non-tool-friendy methods from being called.
                return new ToolStageInjector(PrimaryInjector());
            }
            else
            {
                return PrimaryInjector();
            }
        }

        /// <summary>
        /// 初始化并验证所有相关事务
        /// </summary>
        private void InitializeStatically()
        {
            bindingProcesor.InitializeBindings();
            stopwatch.ResetAndLog("Binding initialization");

            foreach (InjectorShell shell in shells)
            {
                shell.Injector.Index();
            }
            stopwatch.ResetAndLog("Binding indexing");

            injectionRequestProcessor.Process(shells);
            stopwatch.ResetAndLog("Collecting injection requests");

            bindingProcesor.RunCreationListeners();
            stopwatch.ResetAndLog("Binding validation");

            injectionRequestProcessor.Validate();
            stopwatch.ResetAndLog("Static validation");

            initializer.ValidateOustandingInjections(errors);
            stopwatch.ResetAndLog("Instance member validation");

            new LookupProcessor(errors).Process(shells);
            foreach (InjectorShell shell in shells)
            {
                ((DeferredLookups)shell.Injector.lookups).Initialize(errors);
            }
            stopwatch.ResetAndLog("Provider verification");

            foreach (InjectorShell shell in shells)
            {
                if (shell.Elements.Count != 0)
                {
                    throw new AssertionError("Failed to execute " + shell.Elements);
                }
            }

            errors.ThrowCreationExceptionIfErrorsExist();
        }

        /// <summary>
        /// 返回将被构造的注入器。这对root注入器不是必须的
        /// </summary>
        /// <returns></returns>
        private Injector PrimaryInjector()
        {
            return shells[0].Injector;
        }

        /// <summary>
        /// 注入能够被注入的所有事务。此方法是特意不同步的。当注入成员时（例如运行用户代码）如果我们被锁定，
        /// 用户代码从其他线程构建一个即时的binding时将可能死锁
        /// </summary>
        private void InjectDynamically()
        {
            injectionRequestProcessor.InjectMembers();
            stopwatch.ResetAndLog("Static member injection");

            initializer.InjectAll(errors);
            stopwatch.ResetAndLog("Instance injection");
            errors.ThrowCreationExceptionIfErrorsExist();

            if (shellBuilder.GetStage() != Inject.Stage.TOOL)
            {
                foreach (InjectorShell shell in shells)
                {
                    //TODO:执行此方法，堆栈溢出
                    LoadEagerSingletons(shell.Injector, shellBuilder.GetStage(), errors);
                }
                stopwatch.ResetAndLog("Preloading singletons");
            }
            errors.ThrowCreationExceptionIfErrorsExist();
        }

        private class AsyncVoidContextualCallable : ContextualCallable<AsyncVoid>
        {
            Dependency dependency;
            IBindingImpl binding;
            Errors errors;
            public AsyncVoidContextualCallable(IBindingImpl binding, Errors errors)
            {
                this.binding = binding;
                this.dependency = Dependency<object>.GetForNoGeneric(binding.Key);
                this.errors = errors;
            }

            public AsyncVoid Call(InternalContext context)
            {
                Dependency previous = context.SetDependency(dependency);
                Errors errorsForBinding = errors.WithSource(dependency);
                try
                {
                    binding.GetInternalFactory().Get(errorsForBinding, context, dependency, false);
                }
                catch (ErrorsException e)
                {
                    errorsForBinding.Merge(e.Errors);
                }
                finally
                {
                    context.SetDependency(previous);
                }

                return null;
            }
        }

        /// <summary>
        /// 加载之前的单例、或者所有单例（如果我们在 Stage.PRODUCTION场景下）。
        /// 当我们不提前绑定那些单例时，bangdings将被发现
        /// </summary>
        /// <param name="injector"></param>
        /// <param name="stage"></param>
        /// <param name="errors"></param>
        void LoadEagerSingletons(InjectorImpl injector, Stage stage, Errors errors)
        {
            // casting Collection<Binding> to Collection<BindingImpl> is safe
            IEnumerable<IBinding> candidateBindings = ImmutableList<IBinding>.CopyOf<IBinding>(
                injector.state.GetExplicitBindingsThisLevel().Values.Concat(injector.jitBindings.Values));
            foreach (IBindingImpl binding in candidateBindings)
            {
                if (IsEagerSingleton(injector, binding, stage))
                {
                    try
                    {
                        injector.CallInContext(new AsyncVoidContextualCallable(binding, errors));
                    }
                    catch (ErrorsException e)
                    {
                        throw new AssertionError();
                    }
                }
            }
        }

        private bool IsEagerSingleton(InjectorImpl injector, IBindingImpl binding, Stage stage)
        {
            if (binding.Scoping.IsEagerSingleton(stage))
            {
                return true;
            }

            // handle a corner case where a child injector links to a binding in a parent injector, and
            // that binding is singleton. We won't catch this otherwise because we only iterate the child's
            // bindings.
            if (binding is ILinkedBindingImpl)
            {
                Key linkedBinding = ((ILinkedBindingImpl)binding).LinkedKey;
                return IsEagerSingleton(injector, injector.GetBinding(linkedBinding), stage);
            }

            return false;
        }

        /// <summary>
        /// 在工具场景下暴露给用户的注入器
        /// </summary>
        internal class ToolStageInjector : Injector
        {
            private readonly Injector delegateInjector;

            internal ToolStageInjector(Injector delegateInjector)
            {
                this.delegateInjector = delegateInjector;
            }

            public void InjectMembers(object o)
            {
                throw new NotSupportedException(
                  "Injector.injectMembers(Object) is not supported in Stage.TOOL");
            }

            public IDictionary<Key<K>, IBinding<I>> GetBindings<K, I>()
            {
                return this.delegateInjector.GetBindings<K, I>();
            }

            public IDictionary<Key<K>, IBinding<I>> GetAllBindings<K, I>()
            {
                return this.delegateInjector.GetAllBindings<K, I>();
            }

            public IBinding GetBinding(Key key)
            {
                return this.delegateInjector.GetBinding(key);
            }

            public IBinding<T> GetBinding<T>()
            {
                return this.delegateInjector.GetBinding<T>();
            }

            public IBinding<T> GetExistingBinding<T>(Key<T> key)
            {
                return this.delegateInjector.GetExistingBinding<T>(key);
            }

            public IList<IBinding<T>> FindBindingsByType<T>()
            {
                return this.delegateInjector.FindBindingsByType<T>();
            }

            public Injector Parent
            {
                get { return this.delegateInjector.Parent; }
            }

            public Injector CreateChildInjector<T>(IEnumerable<T> modules) where T : IModule
            {
                return delegateInjector.CreateChildInjector(modules);
            }

            public Injector CreateChildInjector(params IModule[] modules)
            {
                return delegateInjector.CreateChildInjector(modules);
            }

            public IDictionary<Type, IScope> ScopeBindings
            {
                get { return delegateInjector.ScopeBindings; }
            }

            public ISet<TypeConverterBinding> TypeConverterBindings
            {
                get { return delegateInjector.TypeConverterBindings; }
            }

            public IProvider<T> GetProvider<T>(Key<T> key)
            {
                throw new NotSupportedException("Injector.getProvider(Key<T>) is not supported in Stage.TOOL");
            }

            public IProvider GetProvider(Type providerGenericType)
            {
                throw new NotSupportedException("Injector.getProvider(Class<T>) is not supported in Stage.TOOL");
            }

            public IMembersInjector<T> GetMembersInjector<T>()
            {
                throw new NotSupportedException("Injector.getMembersInjector<T>() is not supported in Stage.TOOL");
            }

            public IMembersInjector GetMembersInjector(Type genericType)
            {
                throw new NotSupportedException("Injector.getMembersInjector(Type) is not supported in Stage.TOOL");
            }

            public T GetInstance<T>(Key<T> key)
            {
                throw new NotSupportedException("Injector.getInstance(Key<T>) is not supported in Stage.TOOL");
            }

            public object GetInstance(Type clazz)
            {
                throw new NotSupportedException("Injector.getInstance(Type) is not supported in Stage.TOOL");
            }

            IDictionary<Key, IBinding> Injector.Bindings
            {
                get 
                {
                    return this.delegateInjector.Bindings;
                }
            }

            IDictionary<Key, IBinding> Injector.AllBindings
            {
                get 
                {
                    return this.delegateInjector.AllBindings;
                }
            }
        }
    }
}
